#include "GeometryManager.h"

#include "GL/glew.h"
#include "Shader.h"
//#include "LightManager.h"
#include "OpenGLDebug.h"
//#include "GameManager.h"

#ifndef BUFFER_OFFSET
#define BUFFER_OFFSET(bytes) ((GLubyte*) NULL + bytes)
#endif


#define STRINGIFY(A) #A // Used for shader source in .cpp


GeometryManager* GeometryManager::_instance = 0x0; // Needed for singleton to work
std::map<std::string, GeomData*> GeometryManager::geomData;


GeometryManager* GeometryManager::Instance()
{
	if( !_instance )
	{
		_instance = new GeometryManager();
	}
	return _instance;
}

// \Creates a box and adds it to geomData list. Only for testing. Not needed for use.
void GeometryManager::init()
{
	/* CREATE A BOX */ 

	GeomData* boxData = new GeomData();
	boxData->name = "box";
	//boxData->drawBoundingBox = false;
	boxData->primitiveMode = GL_QUADS;


	const char *vertShader = STRINGIFY(
		#version 130

		in vec3 vVertex;
		in vec3 vNormal;
		in vec3 vColor;
		in vec2 vTexCoord;

		out vec3 fColor;
		out vec3 fNormal;

		void main()
		{
			fColor = vColor;
			fNormal = normalize(vNormal);

			gl_Position = gl_ModelViewProjectionMatrix * vVertex;
		}

	);

	const char *fragShader = STRINGIFY(
		#version 130 

		in vec3 fColor;
		in vec3 fNormal;
		in vec2 vTexCoord;

		out vec4 out_Color;


		void main()
		{
			vec3 myColor = vec3(1,1,0);
			out_Color = vec4( myColor, 1.0 );

			out_Color = vec4( (fNormal+1)*0.5  , 0.1);	

		}

	);


	Shader* shader = new Shader();
	if ( shader->install("shaders/Normals.vert", "shaders/Normals.frag") == false )
	{
		printf("failed to install box shader\n");
		return;
	}
	//shader->installFromCString( vertShader, fragShader );
	boxData->shader = shader;
	createBox(boxData->vertices , boxData->texCoords, boxData->normals, 10.0f, 10.0f, 10.0f);
	initVAO(boxData);
	geomData.insert(std::pair<std::string, GeomData*>("box", boxData));
}

void GeometryManager::destroy()
{
	// TODO will this call dtor on all new-ed geomdatas?
	geomData.erase(geomData.begin(), geomData.end());
}

// \Made for STL format, STL is just a list of triangles with a face normal: 3 vertices + 1 normal
void GeometryManager::CreateTriangleMesh(std::string meshName, std::vector<XYZ>& vertices, std::vector<XYZ>& normals)
{
	GeomData* triMesh = new GeomData;
	triMesh->primitiveMode = GL_TRIANGLES;

	// TODO why new a shader? Shaders should not be duplicated!!!
	Shader* shader = new Shader;
	shader->install("shaders/Normals.vert", "shaders/Normals.frag");
	triMesh->shader = shader;
	triMesh->vertices = vertices;

	// hack, copy face normal for 3 verts, could save normals as a normal map texture and use shader to get normal instead
	triMesh->normals.resize( vertices.size() );

	size_t normIdx = 0;
	size_t count=0;
	for(size_t i=0; i<vertices.size(); i++)
	{
		count++;
		glm::vec3 n = glm::normalize( glm::vec3(normals[normIdx].x, normals[normIdx].y, normals[normIdx].z) );
		normals[normIdx] = XYZ( n );
		triMesh->normals[i].x = normals[normIdx].x;
		triMesh->normals[i].y = normals[normIdx].y;
		triMesh->normals[i].z = normals[normIdx].z;
		if( count > 2 )
		{
			count = 0;
			normIdx++;
		}
	}

	// Set BBox Bounds. Faster to do it after fileload
#ifdef _BBOX
	triMesh->boundingBox.reset();
	for(size_t i=0; i<triMesh->vertices.size(); i++)
	{
		triMesh->boundingBox.insert( triMesh );
	}

	// Create Bounding Box Geom
	Vector3D boxMin = triMesh->boundingBox.getPointMin();
	Vector3D boxMax = triMesh->boundingBox.getPointMax();
	// frame 1, min-z
	triMesh->boundingBoxVerts.resize(8);
	triMesh->boundingBoxVerts[0] = Vector3D(boxMin[0], boxMin[1], boxMin[2]);
	triMesh->boundingBoxVerts[1] = Vector3D(boxMin[0], boxMin[1], boxMax[2]);
	triMesh->boundingBoxVerts[2] = Vector3D(boxMax[0], boxMin[1], boxMax[2]);
	triMesh->boundingBoxVerts[3] = Vector3D(boxMax[0], boxMin[1], boxMin[2]);

	triMesh->boundingBoxVerts[4] = Vector3D(boxMin[0], boxMax[1], boxMin[2]);
	triMesh->boundingBoxVerts[5] = Vector3D(boxMin[0], boxMax[1], boxMax[2]);
	triMesh->boundingBoxVerts[6] = Vector3D(boxMax[0], boxMax[1], boxMax[2]);
	triMesh->boundingBoxVerts[7] = Vector3D(boxMax[0], boxMax[1], boxMin[2]);

	triMesh->drawBoundingBox = true;
#endif

	initVAO(triMesh);

	// create BBox Vao
#ifdef _BBOX
	initBBoxVao(triMesh);
#endif
	geomData.insert(std::pair<std::string, GeomData*>(meshName, triMesh));
}

void GeometryManager::CreateTriangleMesh(std::string meshName,
										 GLenum primitiveMode,
										 std::vector<XYZ>& vertices,
										 std::vector<XYZ>& normals,
										 std::vector<unsigned int>& indices)
{
	GeomData* triMesh = new GeomData();
	triMesh->name = meshName;
	triMesh->primitiveMode = primitiveMode;

	Shader* shader = new Shader();
	shader->install("shaders/Normals.vert", "shaders/Normals.frag");
	triMesh->shader = shader;
	triMesh->vertices = vertices;
	triMesh->indices = indices;
	triMesh->normals = normals;

#ifdef _BBOX
	// Set BBox Bounds. Faster to do it after fileload
	triMesh->boundingBox.reset();
	for(size_t i=0; i<triMesh->vertices.size(); i++)
	{
		triMesh->boundingBox.insert( triMesh->vertices[i] );
	}
	// Create Bounding Box Geom
	Vector3D boxMin = triMesh->boundingBox.getPointMin();
	Vector3D boxMax = triMesh->boundingBox.getPointMax();
	// frame 1, min-z
	triMesh->boundingBoxVerts.resize(8);
	triMesh->boundingBoxVerts[0] = Vector3D(boxMin[0], boxMin[1], boxMin[2]);
	triMesh->boundingBoxVerts[1] = Vector3D(boxMin[0], boxMin[1], boxMax[2]);
	triMesh->boundingBoxVerts[2] = Vector3D(boxMax[0], boxMin[1], boxMax[2]);
	triMesh->boundingBoxVerts[3] = Vector3D(boxMax[0], boxMin[1], boxMin[2]);

	triMesh->boundingBoxVerts[4] = Vector3D(boxMin[0], boxMax[1], boxMin[2]);
	triMesh->boundingBoxVerts[5] = Vector3D(boxMin[0], boxMax[1], boxMax[2]);
	triMesh->boundingBoxVerts[6] = Vector3D(boxMax[0], boxMax[1], boxMax[2]);
	triMesh->boundingBoxVerts[7] = Vector3D(boxMax[0], boxMax[1], boxMin[2]);

	triMesh->drawBoundingBox = true;

	// create BBox Vao
	initBBoxVao(triMesh);
#endif
	initVAO(triMesh);


	geomData.insert(std::pair<std::string, GeomData*>(meshName, triMesh));
}

void GeometryManager::createBox(std::vector<XYZ>& vertices, std::vector<UV>& tex, std::vector<XYZ>& normals, float width, float height, float breadth)
{
	width /= 2.0f;
	breadth /= 2.0f;

	vertices.push_back(XYZ(-width, 0.f, breadth));
	vertices.push_back(XYZ(width, 0.f, breadth));
	vertices.push_back(XYZ(width, height, breadth));
	vertices.push_back(XYZ(-width, height, breadth));

	normals.push_back(XYZ(0.f, 0.f, 1.f));
	normals.push_back(XYZ(0.f, 0.f, 1.f));
	normals.push_back(XYZ(0.f, 0.f, 1.f));
	normals.push_back(XYZ(0.f, 0.f, 1.f));

	vertices.push_back(XYZ(width, 0.f, breadth));
	vertices.push_back(XYZ(width, 0.f, -breadth));
	vertices.push_back(XYZ(width, height, -breadth));
	vertices.push_back(XYZ(width, height, breadth));

	normals.push_back(XYZ(1.f, 0.f, 0.f));
	normals.push_back(XYZ(1.f, 0.f, 0.f));
	normals.push_back(XYZ(1.f, 0.f, 0.f));
	normals.push_back(XYZ(1.f, 0.f, 0.f));

	vertices.push_back(XYZ(width, 0, -breadth));
	vertices.push_back(XYZ(-width, 0, -breadth));
	vertices.push_back(XYZ(-width, height, -breadth));
	vertices.push_back(XYZ(width, height, -breadth));

	normals.push_back(XYZ(0.f, 0.f, -1.f));
	normals.push_back(XYZ(0.f, 0.f, -1.f));
	normals.push_back(XYZ(0.f, 0.f, -1.f));
	normals.push_back(XYZ(0.f, 0.f, -1.f));

	vertices.push_back(XYZ(-width, 0, -breadth));
	vertices.push_back(XYZ(-width, 0, breadth));
	vertices.push_back(XYZ(-width, height, breadth));
	vertices.push_back(XYZ(-width, height, -breadth));

	normals.push_back(XYZ(-1.f, 0.f, 0.f));
	normals.push_back(XYZ(-1.f, 0.f, 0.f));
	normals.push_back(XYZ(-1.f, 0.f, 0.f));
	normals.push_back(XYZ(-1.f, 0.f, 0.f));

	vertices.push_back(XYZ(-width, height, breadth));
	vertices.push_back(XYZ(width, height, breadth));
	vertices.push_back(XYZ(width, height, -breadth));
	vertices.push_back(XYZ(-width, height, -breadth));

	normals.push_back(XYZ(0.f, 1.f, 0.f));
	normals.push_back(XYZ(0.f, 1.f, 0.f));
	normals.push_back(XYZ(0.f, 1.f, 0.f));
	normals.push_back(XYZ(0.f, 1.f, 0.f));

	vertices.push_back(XYZ(-width, 0.f, -breadth));
	vertices.push_back(XYZ(width, 0.f, -breadth));
	vertices.push_back(XYZ(width, 0.f, breadth));
	vertices.push_back(XYZ(-width, 0.f, breadth));

	normals.push_back(XYZ(0.f, -1.f, 0.f));
	normals.push_back(XYZ(0.f, -1.f, 0.f));
	normals.push_back(XYZ(0.f, -1.f, 0.f));
	normals.push_back(XYZ(0.f, -1.f, 0.f));

	vertices.push_back(XYZ(-width, 0.f, breadth));
	vertices.push_back(XYZ(width, 0.f, breadth));
	vertices.push_back(XYZ(width, height, breadth));
	vertices.push_back(XYZ(-width, height, breadth));

	normals.push_back(XYZ(0.0f, 0.0f, 1.0f));
	normals.push_back(XYZ(0.0f, 0.0f, 1.0f));
	normals.push_back(XYZ(0.0f, 0.0f, 1.0f));
	normals.push_back(XYZ(0.0f, 0.0f, 1.0f));

	for ( int i = 0; i < 7; i++)
	{
		tex.push_back(UV(0.0f, 1.0f));
		tex.push_back(UV(1.0f, 1.0f)); 
		tex.push_back(UV(1.0f, 0.0f));
		tex.push_back(UV(0.0f, 0.0f)); 
	}
}

void setAttrib(GeomData *data, const char* strAttribName, int count, unsigned int gl_type, unsigned int offset)
{
	unsigned int program_handle = data->shader->getProgram();
	int id = glGetAttribLocation(program_handle, strAttribName);
	if( id != -1)
	{
		glVertexAttribPointer(id, count, gl_type, GL_FALSE, 0, BUFFER_OFFSET(offset));
		glEnableVertexAttribArray(id);
	}else{
		printf("Error: glGetAttribLocation failed on setting %s on geomdata %s\n", strAttribName, data->name.c_str()  );
	}
}

void GeometryManager::initVAO(GeomData* data)
{
	// do you gain any perf by interleaving data? for example in order VertexNormalTexture?
	// http://www.opengl.org/wiki/Vertex_Specification_Best_Practices
	// meh. only 5% says gl wiki
	unsigned int vertSize = sizeof(float)*3*data->vertices.size();
	unsigned int texSize = sizeof(float)*2*data->texCoords.size();
	unsigned int normSize = sizeof(float)*3*data->normals.size();
	unsigned int colorSize = sizeof(float)*3*data->colors.size();
	unsigned int indSizeBytes = sizeof(unsigned int)*data->indices.size();

	unsigned int vertOffset = 0;
	unsigned int texOffset = vertOffset + vertSize;
	unsigned int normOffset = texOffset + texSize;
	unsigned int colorOffset = normOffset + normSize;

	data->shader->begin();
	glGenVertexArrays(1, &data->vao);
	glBindVertexArray(data->vao);

	// is our vertex data indiced?
	if( indSizeBytes > 0 )
	{
		glGenBuffers(1, &data->ibo);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data->ibo);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indSizeBytes, &data->indices[0], GL_STATIC_DRAW);
	}

	glGenBuffers(1, &data->vbo);
	glBindBuffer(GL_ARRAY_BUFFER, data->vbo);
	glBufferData(GL_ARRAY_BUFFER, vertSize+texSize+normSize+colorSize, NULL, GL_STATIC_DRAW);

	// http://www.opengl.org/wiki/GlVertexAttribPointer
	int id = 0;
	if( vertSize > 0 )
	{
		glBufferSubData(GL_ARRAY_BUFFER, vertOffset, vertSize, &data->vertices[0]);
		setAttrib(data, "vVertex", 3, GL_FLOAT, vertOffset);
	}

	if( texSize > 0 )
	{
		glBufferSubData(GL_ARRAY_BUFFER, texOffset, texSize, &data->texCoords[0]);
		setAttrib(data, "vTexCoord", 2, GL_FLOAT, texOffset);
	}

	if( normSize > 0)
	{
		glBufferSubData(GL_ARRAY_BUFFER, normOffset, normSize, &data->normals[0]);
		setAttrib(data, "vNormal", 3, GL_FLOAT, normOffset);
	}

	if( colorSize > 0)
	{
		glBufferSubData(GL_ARRAY_BUFFER, colorOffset, colorSize, &data->colors[0]);
		setAttrib(data, "vColor", 3, GL_FLOAT, colorOffset);
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glUseProgram(0);
}

#ifdef _BBOX
void GeometryManager::initBBoxVao(GeomData* data)
{
	unsigned int vertSize = sizeof(float)*3*data->boundingBoxVerts.size();
	unsigned int vertOffset = 0;

	glGenVertexArrays(1, &data->vaoBBox);
	glBindVertexArray(data->vaoBBox);

	glGenBuffers(1, &data->vboBBox);
	glBindBuffer(GL_ARRAY_BUFFER, data->vboBBox);
	glBufferData(GL_ARRAY_BUFFER, vertSize, NULL, GL_STATIC_DRAW);

	int id = 0;
	if( vertSize > 0 )
	{
		glBufferSubData(GL_ARRAY_BUFFER, vertOffset, vertSize, &data->boundingBoxVerts[0]);
		id = glGetAttribLocation(data->shader->getProgram(), "vVertex");
		glVertexAttribPointer(id, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(vertOffset));
		glEnableVertexAttribArray(id); 
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}
#endif

GeomData* GeometryManager::getGeomData( std::string type )
{
	// Don't directly look up in the map, if the type doesn't exist a new one is created with default ctor and returned
	// instead, see if the value exists in the map, and return null if it doesn't
	std::map<std::string, GeomData*>::iterator it = geomData.find(type);

	if( it == geomData.end() )
		return 0x0;
	else
		return geomData[type];

}

void GeomData::render()
{
	shader->begin();
	{
		//glActiveTexture(GL_TEXTURE1);
		//glBindTexture(GL_TEXTURE_2D, texture);
		//glUniform1i(glGetUniformLocation(shader->getProgram(), "tex0"), 1);
		//unsigned int i = glGetUniformLocation(shader->getProgram(), "tex0");

		glBindVertexArray(vao);
		if( indices.size() > 0 )
		{
			glDrawElements(primitiveMode, indices.size(), GL_UNSIGNED_INT, BUFFER_OFFSET(0));
		}else
		{
			glDrawArrays(primitiveMode, 0, vertices.size()); 
		}
		glBindVertexArray(0);
	}shader->end();

#ifdef _BBOX
	if( drawBoundingBox )
	{
		glColor3f(1,0,0);
		glPointSize(3);
		glBindVertexArray(vaoBBox);
		glDrawArrays(GL_POINTS, 0, boundingBoxVerts.size() ); 
		glBindVertexArray(0);
	}
#endif
}



void GeomData::renderDepth()
{
	//glUseProgram(0);
	//glBegin(GL_POINTS);
	//	for (size_t i=0; i<vertices.size(); i++)
	//	{
	//		glVertex3fv( &vertices[i].x );
	//	}
	//	
	//glEnd();

	glBindVertexArray(vao);

	if( indices.size() > 0 )
	{
		glDrawElements(primitiveMode, indices.size(), GL_UNSIGNED_INT, BUFFER_OFFSET(0));
	}else
	{
		glDrawArrays(primitiveMode, 0, vertices.size()); 
	}
	glBindVertexArray(0);
}
